<?php

/**
 * Redis 缓存
 *
 * @author 海啸 <459103503@qq.com>
 */
class RedisHandle
{
    //redis对象
    private $_redis;

    //保存类实例的静态成员变量
    private static $_instance;

    /**
     * 私有构造，保证单例模式入口统一
     *
     * @param array $config redis认证
     */
    private function __construct($config = array())
    {
        if (empty($config)) {
            $config = array('server' => C('REDIS_HOST'), 'port' => C('REDIS_PORT'));
        }
        $this->redis = new Redis();
        try{
            $this->redis->connect($config['server'], $config['port']);
            $this->redis->ping();
        }catch (Exception $e){
            throw_exception("RedisHandle_redis_connect 3 ".$e->getMessage());
        }
        return $this->redis;
    }

    /**
     * 创建__clone方法防止对象被复制克隆
     *
     * @return void
     */
    private  function __clone()
    {
        trigger_error('Clone is not allow!', E_USER_ERROR);
    }

    /**
     * 单例方法,用于访问实例的公共的静态方法
     *
     * @param array $config redis配置信息
     *
     * @return Single
     */
    public static function getInstance($config = array())
    {
        if (!(self::$_instance instanceof self)) {
            self::$_instance = new self($config);
        }
        return self::$_instance;
    }

    /**
     * 设置值(string)会将$value自动转为json格式
     *
     * @param string $key     KEY名称
     * @param mixed  $value   获取得到的数据
     * @param int    $timeOut 时间
     *
     * @return bool
     */
    public function set($key, $value, $timeOut = 0)
    {
        $value = json_encode($value, true);

        //设置键值
        $retRes = $this->redis->set($key, $value);

        //设置key生命周期
        if ($timeOut > 0) {
            $this->redis->setTimeout($key, $timeOut);
        }

        return $retRes;
    }

    /**
     * 通过KEY获取数据(string)
     *
     * @param string $key KEY名称
     *
     * @return string
     */
    public function get($key)
    {
        $result = $this->redis->get($key);

        return json_decode($result, true);
    }

    /**
     * 删除一条数据
     *
     * @param string $key KEY名称
     *
     * @return int
     */
    public function delete($key)
    {
        return $this->redis->del($key);
    }

    /**
     * 同时删除多个key数据
     *
     * @param array $keyArr KEY集合
     *
     * @return int
     */
    public function dels($keyArr)
    {
        if (is_array($keyArr)) {
            return $this->redis->del($keyArr);
        } else {
            return 0;
        }
    }

    /**
     * 数据入队列(对应redis的list数据结构)
     *
     * @param string $key   KEY名称
     * @param mixed  $value 需压入的数据
     * @param bool   $right 是否从右边开始入
     *
     * @return int
     */
    public function push($key, $value, $right = true)
    {
        $value = json_encode($value, true);
        return $right ? $this->redis->rPush($key, $value) : $this->redis->lPush($key, $value);
    }

    /**
     * 数据出队列（对应redis的list数据结构
     *
     * @param string $key  KEY名称
     * @param bool   $left 是否从左边开始出数据
     *
     * @return mixed
     */
    public function pop($key, $left = true)
    {
        $val = $left ? $this->redis->lPop($key) : $this->redis->rPop($key);

        return json_decode($val, true);
    }

    /**
     * 清空数据
     *
     * @return bool
     */
    public function flushAll()
    {
        return $this->redis->flushAll();
    }

    /**
     * 数据自增
     *
     * @param string $key KEY名称
     *
     * @return int
     */
    public function increment($key)
    {
        return $this->redis->incr($key);
    }

    /**
     * 数据自减
     *
     * @param string $key KEY名称
     *
     * @return int
     */
    public function decrement($key)
    {
        return $this->redis->decr($key);
    }

    /**
     * key是否存在
     *
     * @param string $key KEY名称
     *
     * @return bool
     */
    public function exists($key)
    {
        return $this->redis->exists($key);
    }

    /**
     * 返回redis对象
     *
     * @return Redis
     */
    public function redis()
    {
        return $this->redis;
    }

    /**
     * 设置多个值
     *
     * @param array $dataArr 数据键值对集合
     * @param int   $timeOut 过期时间
     *
     * @return bool
     */
    public function sets($dataArr, $timeOut)
    {
        if (is_array($dataArr)) {
            $retRes = $this->redis->mset($dataArr);
            if ($timeOut > 0) {
                foreach ($dataArr as $key => $value) {
                    $this->redis->expire($key, $timeOut);
                }
            }
            return $retRes;
        } else {
            return false;
        }
    }

    /**
     * 同时获取多个值
     *
     * @param array $keyArr key值集合
     *
     * @return array
     */
    public function gets($keyArr)
    {
        if (is_array($keyArr)) {
            return $this->redis->mget($keyArr);
        } else {
            return null;
        }
    }

    /**
     * 获取所有key
     *
     * @return array
     */
    public function keyAll()
    {
        return $this->redis->keys('*');
    }

    /**
     * 获取缓存
     *
     * @param string $key 键值
     *
     * @desc 使用锁机制防大量缓存过期时所产生的惊群现象保证只有一个进程不获取数据，可以更新，其他进程仍然获取过期数据
     * @return mixed
     */
    public function getByLock($key)
    {
        $sth = $this->redis->get($key);

        if ($sth === false) {
            return $sth;
        } else {
            $sth = json_decode($sth, true);

            if (intval($sth['expire']) <= time()) {
                $lock = $this->redis->incr($key . ".lock");
                if ($lock === 1) {
                    return false;
                } else {
                    return $sth['data'];
                }
            } else {
                return $sth['data'];
            }
        }
    }

    /**
     * 设置缓存
     *
     * @param string $key   缓存键
     * @param mixed  $value 缓存值
     * @param int    $ttl   生命周期
     *
     * @desc 增加缓存时间来避免惊群现象
     * @return bool
     */
    public function setByLock($key, $value, $ttl = 0)
    {
        if (intval($ttl) > 0) {
            $ttl = intval($ttl);
            $exp = time() + $ttl;
        } else {
            $ttl = 300;
            $exp = time() + $ttl;
        }

        $arg = array("data" => $value, "expire" => $exp);
        $rs = $this->redis->setex($key, $ttl, json_encode($arg, true));
        $this->redis->del($key . ".lock");
        return $rs;
    }

    /**
     * 设置过期时间
     *
     * @param string $key     KEY
     * @param int    $timeOut 过期时间
     *
     * @return int
     */
    public function setTimeOut($key, $timeOut)
    {
        return $this->redis->setTimeout($key, $timeOut);
    }

    /**
     * 获取过期剩余时间
     *
     * @param string $key KEY
     *
     * @return int
     */
    public function getTimeOut($key)
    {
        return $this->redis->ttl($key);
    }


}